home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
TPUG - Toronto PET Users Group
/
TPUG Users Group CD
/
TPUG Users Group CD.iso
/
AMIGA
/
AMICUS
/
AMICUS24.ADF
/
CodeDemo
/
CodeDemo.mod
< prev
next >
Wrap
Text File
|
1988-04-16
|
23KB
|
504 lines
(* Authors = Steve Wilkinson and J.E. Gilpin
Developed using Modula-2/Amiga TDI Software Inc. Dallas,Texas
Asm68k 68010 Macro Assembler V 1.2.1
Released to Public Domain Sept. 1987.
Permission to Alter, Copy and Redistribute granted by Authors.*)
(* The sole purpose of this Program is to demonstrate the use
of Assembly language routines in Modula-2 CODE Statements.
The functions performed by the Assembly Language routine
can be more efficiently done using built-in Amiga procedures
and co-processor hardware.
Unless you completely understand Modula-2 and Amiga register
conventions, you may find it difficult to use Assembly Language
routines in Modula-2 CODE statements. Registers A0 and D0 are
listed as scratch registers in most Amiga literature, so it
should be safe to use them for In-line Assembly routines.
If you want to use more registers, preserve the State of the
machine as completely as possible.
The key to successful Assembly routines in Modula-2 CODE
statements is register preservation.
You can achieve that by:
1. Passing parameters to your routine using a scratch
register such as A0.
2. Pushing all registers to the stack at the beginning
of your routine and restoring them at the end.
In addition, defining data memory in Modula-2 and passing
a data pointer to the Assembly routine seems to work better
than trying to create data space in Assembly routine.
The final routine must be derived from the Assembler
object file output. An executable routine contains loader
information and may not work as in-line code in Modula-2.
Symbolic external references must be avoided since they
require resolution by a linker. *)
(* The Program "AsmToCode" is included with this ARC file.
It automatically converts Assembler object files into
a Modula-2 CODE statement. You can manually convert
the object file listing into numerical text, but that can
be a tedious process at best.
"AsmToCode" simplifies the process. To use "AsmToCode"
just enter "AsmToCode <objectfilename>" from CLI.
The program will produce a file with the same name as
the original, but with the extension stripped and the
extension ".cnv" added. *)
MODULE CODEDemo;
FROM InOut IMPORT WriteString, WriteLn;
FROM SYSTEM IMPORT NULL, ADDRESS, ADR, BYTE, CODE, ASH,
REGISTER,SETREG,TSIZE;
FROM Intuition IMPORT ScreenPtr, IntuitionBase, IntuitionName,
CustomScreen;
FROM GraphicsLibrary IMPORT GraphicsName, GraphicsBase,
DrawingModeSet, DrawingModes;
FROM Pens IMPORT Move, RectFill, SetAPen, SetDrMd;
FROM Rasters IMPORT RastPortPtr, SetRast;
FROM Views IMPORT Modes, ModeSet,WaitTOF;
FROM Screens IMPORT OpenScreen, CloseScreen, NewScreen;
FROM Text IMPORT Text;
FROM Libraries IMPORT OpenLibrary, CloseLibrary;
FROM DOSProcessHandler IMPORT Delay;
FROM Nodes IMPORT Node,NodeType;
FROM Tasks IMPORT Task,TaskPtr,AddTask,RemTask;
FROM Interrupts IMPORT Interrupt,InterruptPtr,AddIntServer,
RemIntServer,Forbid,Permit;
FROM Memory IMPORT AllocMem,FreeMem,MemReqSet,MemClear,
MemChip,MemPublic;
FROM Sprites IMPORT SimpleSprite,GetSprite,MoveSprite,
ChangeSprite,FreeSprite;
TYPE
codeinfotype = RECORD
BPlane1 : ADDRESS;
BPlane2 : ADDRESS;
count : CARDINAL; (* word counter for picture *)
END; (* RECORD codeinfo *)
SpriteImage = ARRAY [0..17] OF CARDINAL;
SpriteImagePtr = POINTER TO SpriteImage;
VAR (* We are using lots of Global Variables *)
scrnptr : ScreenPtr; (* because we are seperating this Module *)
ns : NewScreen; (* into a Task, an Interrupt, and a *)
codeinfo: codeinfotype; (* controlling Process. The Globals let *)
x, y : CARDINAL; (* us communicate and transfer data *)
rp : RastPortPtr; (* without suspending execution using *)
RegStore: ADDRESS; (* Wait or WaitPort statements. *)
message : ARRAY [0..30] OF CHAR;
ServiceNode : Node;
WalkTaskPoint : TaskPtr;
WalkSize : LONGCARD;
KillDemo : ARRAY [0..8] OF CHAR;
KillerPoint : InterruptPtr;
Killer : Interrupt;
FlySprite : INTEGER;
Fly : ARRAY [0..3] OF SpriteImagePtr;
SpriteData : SimpleSprite;
number,terminate : CARDINAL;
name : ARRAY [0..7] OF CHAR;
PROCEDURE Kill; (* Keyboard or Disk activity will activate *)
BEGIN (* this Interrupt Server Routine. It will *)
codeinfo.count:=0; (* clear a Global Variable, which our *)
END Kill; (* Assembly Routine will detect and *)
(* respond to by terminating. *)
(* The following Procedure is converted into a Task and *)
(* launched into coprocessing. It communicates with the parent *)
(* process through the Global Variable "terminate". If the *)
(* parent process clears that variable to zero the Task stops *)
(* and waits to be removed. If we let it terminate on its own *)
(* it will corrupt the Operating System. The culprit might be the *)
(* Modula 2 Exit Code. Using the P- Compiler option might cure *)
(* the problem and elimitate the need for the LOOP END statements *)
PROCEDURE Walk;
BEGIN
WHILE (terminate <> 0) DO
WaitTOF; WaitTOF;
ChangeSprite(ADR(scrnptr^.VPort),SpriteData,Fly[number]);
number:=number+1; IF (number > 3) THEN number:=0; END;
END;
terminate:=2; LOOP END;
END Walk;
PROCEDURE MakeInterrupt; (* This Procedure sets up our Interrupt *)
(* Server Routine. Notice that we are *)
BEGIN (* putting a Node directly into a *)
KillerPoint:=ADR(Killer); (* Modula 2 Record instead of doing *)
KillDemo:="KillDemo"; (* an AllocMem and referencing it with *)
KillDemo[8]:=CHR(0); (* Pointer. God Forbid !! I don't know *)
WITH ServiceNode DO (* if the Operating System can relocate *)
lnSucc:=NULL; (* our Modula 2 data after it starts *)
lnPred:=NULL; (* our program. If so we might get into *)
lnType:=BYTE(NTInterrupt); (* trouble doing it this way. *)
lnPri:=BYTE(0);
lnName:=ADR(KillDemo);
END;
WITH Killer DO
isNode:=ServiceNode;
isData:=ADR(codeinfo.count);
isCode:=Kill;
END;
END MakeInterrupt;
PROCEDURE MakeFly; (* This procedure creates the image data *)
(* for the "Walk" Task to manipulate. *)
VAR (* This data must be in Chip memory so the *)
MemAddress:ADDRESS; (* graphics hardware can access it. *)
num:CARDINAL;
BEGIN
Fly[0]:=NULL; number:=0;
MemAddress:=AllocMem(LONG(200),MemReqSet{MemClear,MemChip,MemPublic});
IF (MemAddress<>0) THEN
Fly[0]:=MemAddress; Fly[1]:=MemAddress+LONG(50);
Fly[2]:=MemAddress+LONG(100); Fly[3]:=MemAddress+LONG(150);
FOR num:=0 TO 17 DO Fly[0]^[num]:=00000H; END;
Fly[0]^[0]:=50; Fly[0]^[1]:=100;
Fly[0]^[3]:=01240H; Fly[0]^[5]:=00A80H;
Fly[0]^[7]:=03FC0H; Fly[0]^[9]:=01FE0H;
Fly[0]^[11]:=03FC0H; Fly[0]^[13]:=00A80H;
Fly[0]^[15]:=01240H;
FOR num:=0 TO 15 DO
Fly[1]^[num]:=Fly[0]^[num]; Fly[2]^[num]:=Fly[0]^[num];
Fly[3]^[num]:=Fly[0]^[num];
END;
Fly[1]^[3]:=02900H; Fly[1]^[5]:=01500H;
Fly[3]^[13]:=01500H; Fly[3]^[15]:=02900H;
WITH SpriteData DO
posCtlData:=Fly[0];
height:=7;
x:=50;
y:=100;
num:=0;
END;
FlySprite:=GetSprite(SpriteData,-1);
IF (FlySprite <> -1) THEN
MoveSprite(ADR(scrnptr^.VPort),SpriteData,50,100);
END;
END;
END MakeFly;
PROCEDURE StartWalk; (* This Procedure Initializes our "Walk" Task *)
(* and launches it into action. It's a direct *)
(* conversion from a C language routine, *)
VAR (* which is why it doesn't make any sense. *)
stacksize,datasize:LONGCARD;
BEGIN
name:="DoWalk"; name[7]:=CHR(0); stacksize:=1000;
datasize:=ASH(stacksize,-2); datasize:=ASH(datasize,2);
WalkSize:=TSIZE(Task)+datasize; terminate:=1;
datasize:=ASH(WalkSize,-1); datasize:=ASH(datasize,1);
WalkTaskPoint:=AllocMem(WalkSize,MemReqSet{MemClear,MemPublic});
IF (WalkTaskPoint <> NULL) THEN
WITH WalkTaskPoint^ DO
WITH tcNode DO
lnType:=BYTE(NTTask);
lnPri:=BYTE(0);
lnName:=ADR(name);
END;
tcSPLower:=ADDRESS(WalkTaskPoint) + TSIZE(Task);
tcSPUpper:=ADDRESS(WalkTaskPoint) + ADDRESS(datasize);
tcSPReg:=tcSPUpper;
END;
AddTask(WalkTaskPoint,ADDRESS(Walk),0);
END;
END StartWalk;
PROCEDURE Setup; (* This Procedure opens the libraries *)
(* and opens our screen. *)
BEGIN
GraphicsBase := OpenLibrary (GraphicsName, 0);
IntuitionBase := OpenLibrary (IntuitionName,0);
IF (IntuitionBase = 0) OR (GraphicsBase = 0)THEN
WriteString ('Library error');
WriteLn;
HALT;
END; (* IF IntuitionBase *)
WITH ns DO
LeftEdge := 0; TopEdge := 0;
Width := 320; Height := 200; Depth := 2; (* don't need 32 clrs*)
DetailPen := BYTE(-1); BlockPen := BYTE(-1); (* any color*)
ViewModes := ModeSet {}; (* no special modes *)
Type := CustomScreen;
Font := NULL; (* no special font *)
DefaultTitle := NULL; (* no screen title *)
Gadgets := NULL; (* no gadgets *)
CustomBitMap := NULL; (* no superbitmap *)
END; (* WITH ns *)
scrnptr := OpenScreen(ADR(ns));
IF scrnptr = NULL THEN
WriteString ('Can`t open your screen'); WriteLn;
CloseLibrary (IntuitionBase);
CloseLibrary (GraphicsBase);
HALT;
END; (* IF scrnptr *)
END Setup;
PROCEDURE PutStuffOnScreen; (* This Procedure puts some stuff on *)
(* our screen to manipulate with our *)
BEGIN (* Assembly Language routine. *)
rp := ADR(scrnptr^.RPort);
SetRast (rp, 0); (* Clear the Screen to black *)
SetAPen (rp, 2); (* Set to color 2 *)
RectFill (rp, 15, 15, 55, 85); (* draw a bunch of *)
RectFill (rp, 100, 150, 250, 199); (* haphazard rects *)
RectFill (rp, 250, 15, 319, 65);
RectFill (rp, 160, 70, 235, 180);
(* Set mode to Inverse video *)
SetDrMd (rp, DrawingModeSet {Complement});
message := 'CODE Demo'; (* What I want to print on the background *)
y := 25;
FOR x := 25 TO 190 BY 20 DO (* print a pretty pattern *)
Move (rp, x, y);
Text (rp, message, 9);(* 9 = num of chars in message*)
INC (y,20);
END; (* x *)
END PutStuffOnScreen;
(* The following Procedure puts the bitplane addresses into a *)
(* record so we can pass them to our Assembly Language routine. *)
(* We pass the address of the record to the Assembly Language *)
(* routine in the A0 register. *)
PROCEDURE InitializeCodeParameters;
BEGIN
WITH codeinfo DO
BPlane1 := scrnptr^.RPort.bitMap^.Planes[0]; (* address plane 1 *)
BPlane2 := scrnptr^.RPort.bitMap^.Planes[1]; (* address plane 2 *)
count := 4000; (* 4000 words in one BitPlane of information *)
END; (* WITH codeinfo *)
END InitializeCodeParameters;
(* The following Procedure shuts down our coprocessing Task "Walk" *)
(* by clearing the Global Variable "terminate". It then waits for *)
(* the Task to answer with a value of 2 in the terminate variable. *)
(* Why ?? Because if we just call RemTask we might remove our Task *)
(* while its executing the WaitTOF routine. If that happens, we *)
(* corrupt the Operating System and can expect to visit the GURU *)
(* when the Program is finished. <It took numerous visits from the *)
(* GURU to figure that one out. Sure wish AMIGA literature would *)
(* warn us about things like that.> The rest of the Procedure just *)
(* cleans up and closes us down. *)
PROCEDURE ShutdownAndCleanup;
BEGIN
IF (WalkTaskPoint <> NULL) THEN
terminate:=0; WHILE (terminate <> 2) DO number:=0; END;
RemTask(WalkTaskPoint);
FreeMem(WalkTaskPoint,WalkSize);
END;
IF (codeinfo.count<>0) THEN number:=50; ELSE number:=10; END;
IF (FlySprite<>-1) THEN
ChangeSprite(ADR(scrnptr^.VPort),SpriteData,Fly[0]);
Delay (LONG(number));
FreeSprite(VAL(CARDINAL,FlySprite));
ELSE Delay (LONG(number));
END;
IF (Fly[0]<>NULL) THEN FreeMem(Fly[0],LONG(200));END;
CloseScreen (scrnptr);
CloseLibrary (IntuitionBase);
CloseLibrary (GraphicsBase);
END ShutdownAndCleanup;
BEGIN
Setup; (* Open Libraries and Screen *)
PutStuffOnScreen; (* Draw some stuff on the Screen *)
InitializeCodeParameters; (* Get Parameters to pass to our CODE *)
MakeInterrupt; (* Setup termination Interrupt *)
MakeFly; (* Create and Display Sprite imagery *)
Delay (LONG(50)); (* Let user see whats here. *)
IF (FlySprite <> -1) THEN StartWalk; END; (* Activate Walk Task *)
AddIntServer(3,KillerPoint); (* Install our Interrupt *)
RegStore := REGISTER(8); (* Store A0 *)
SETREG (8, ADR(codeinfo)); (* Pass parameters to CODE in A0 *)
CODE(18663,65534,17031,15912,8,26368,408,10247,57932,21380,36604,
200,21831,8764,0,4,9832,4,11324,0,320,19048,8,26368,372,8808,
0,9320,4,9276,0,199,9735,17024,57817,25600,6,2240,0,2065,
7,26368,8,2281,0,65535,57818,25600,6,2240,1,2066,7,26368,
8,2282,0,65535,57817,2065,7,26368,8,2281,0,65535,57818,2066,
7,26368,8,2282,0,65535,20939,65502,2217,0,65535,2218,0,65535,
2048,0,26368,6,21033,65535,2048,1,26368,6,21034,65535,20938,
65410,20937,26,10756,10315,8724,2689,32768,32768,10433,20941,
65524,8764,0,4,20942,65356,11324,0,318,19048,8,26368,184,
8808,0,9276,0,199,9735,17024,57817,25600,6,2240,0,2065,7,
26368,8,2281,0,65535,57817,2065,7,26368,8,2281,0,65535,20939,
65518,2217,0,65535,2048,0,26368,6,21033,65535,20938,65468,
20942,65446,11324,0,318,19048,8,26368,86,8808,4,9276,0,199,
9735,17024,57817,25600,6,2240,0,2065,7,26368,8,2281,0,65535,
57817,2065,7,26368,8,2281,0,65535,20939,65518,2217,0,65535,
2048,0,26368,6,21033,65535,20938,65468,20942,65446,19679,
32767);
SETREG (8, RegStore); (* Restore register A0 *)
RemIntServer(3,KillerPoint); (* Remove our Interrupt *)
ShutdownAndCleanup; (* Self Explanatory *)
END CODEDemo.
(* The following is the Assembly listing for the CODE statement *)
(* used in the above Modula-2 Program Module. *)
(*
ORIGIN START
PLANE1 EQUATE $00 ;DECLARE DATA OFFSETS
PLANE2 EQUATE $04 ;FROM PARAMETER REGISTER A0
SIZE EQUATE $08 ;PASSED FROM MODULA2
START MOVEM.L D0-D7/A0-A6,-(A7) ;SAVE REGISTERS ON STACK
CLR.L D7 ;CLEAR PLANE WIDTH REGISTER
MOVE.W SIZE(A0),D7 ;GET PLANE SIZE
BEQ QUIT ;QUIT IF SIZE IS ZERO
MOVE.L D7,D4 ;GET PLANESIZE
LSR #1,D4 ;SHIFT TO HALVE
SUBQ.L #1,D4 ;ADJUST FOR DBRA IN LOOP
DIVU #200,D7 ;CALCULATE PLANE WIDTH IN WORDS
SUBQ.W #2,D7 ;ADJUST FOR USE AS LOOP COUNT
MOVE.L #4,D1 ;INITIALIZE STROBE COUNTER
MOVE.L PLANE2(A0),A3 ;GET PLANE2 ADDRESS FOR STROBE
MOVE.L #320,D6 ;TOTAL REPETITIONS
LOOP3 TST.W SIZE(A0) ;CHECK FOR INTERRUPT
BEQ QUIT ;QUIT IF CLEARED BY INTERRUPT
MOVE.L PLANE1(A0),A1 ;GET PLANE1 ADDRESS
MOVE.L PLANE2(A0),A2 ;GET PLANE2 ADDRESS
MOVE.L #199,D2 ;SET HORIZONTAL SIZE LOOP COUNT
LOOP2 MOVE.L D7,D3 ;RESET LOOP COUNTER
CLR.L D0 ;INITIALIZE BITCATCHER REGISTER
ASL (A1)+ ;START HORIZONTAL SHIFT OF PLANE1
BCC SKIP ;SKIP IF BIT WAS 0
BSET #0,D0 ;SAVE SHIFTED BIT
SKIP BTST.B #7,(A1) ;CHECK HIGH BIT OF NEXT WORD
BEQ SKIP1 ;SKIP IF BIT WAS 0
BSET.B #0,-1(A1) ;MOVE TO LOW BIT OF PREVIOUS WORD
SKIP1 ASL (A2)+ ;START HORIZONTAL SHIFT OF PLANE2
BCC SKIP2 ;SKIP IF BIT WAS 0
BSET #1,D0 ;SAVE SHIFTED BIT
SKIP2 BTST.B #7,(A2) ;CHECK HIGH BIT OF NEXT WORD
BEQ LOOP ;SKIP IF BIT WAS 0
BSET.B #0,-1(A2) ;MOVE TO LOW BIT OF PREVIOUS WORD
LOOP ASL (A1)+ ;SHIFT REST OF LINE IN PLANE1
BTST.B #7,(A1) ;CHECK HIGH BIT OF NEXT WORD
BEQ SKIP3 ;SKIP IF BIT WAS 0
BSET.B #0,-1(A1) ;MOVE TO LOW BIT OF PREVIOUS WORD
SKIP3 ASL (A2)+ ;SHIFT REST OF LINE IN PLANE2
BTST.B #7,(A2) ;CHECK HIGH BIT OF NEXT WORD
BEQ SKIP4 ;SKIP IF BIT WAS 0
BSET.B #0,-1(A2) ;MOVE TO LOW BIT OF PREVIOUS WORD
SKIP4 DBRA D3,LOOP ;LOOP TILL LINE DONE
BCLR.B #0,-1(A1) ;CLEAR LOW BIT OF LAST WORD
BCLR.B #0,-1(A2) ;CLEAR LOW BIT OF LAST WORD
BTST #0,D0 ;CHECK FOR BIT FROM START OF LINE
BEQ SKIP5 ;SKIP IF BIT WAS 0
ADDQ.B #1,-1(A1) ;SHIFT BIT FROM START OF LINE
SKIP5 BTST #1,D0 ;CHECK FOR BIT FROM START OF LINE
BEQ SKIP6 ;SKIP IF BIT WAS 0
ADDQ.B #1,-1(A2) ;SHIFT BIT FROM START OF LINE
SKIP6 DBRA D2,LOOP2 ;LOOP TILL PLANES ARE SHIFTED
DBRA D1,SKIPSTROBE ;SKIP COLOR CHANGE
MOVE.L D4,D5 ;GET PLANESIZE
MOVE.L A3,A4 ;GET PLANE ADDRESS
COLORLOOP MOVE.L (A4),D1 ;GET PLANELONGWORD
EORI.L #$80008000,D1 ;REVERSE BITS
MOVE.L D1,(A4)+ ;REPLACE PLANELONGWORD
DBRA D5,COLORLOOP ;LOOP
MOVE.L #4,D1 ;RESET COUNTER
SKIPSTROBE DBRA D6,LOOP3 ;LOOP TILL SCROLL DONE
;SCROLL PLANE1
MOVE.L #318,D6 ;TOTAL REPETITIONS
LOOP3A TST.W SIZE(A0) ;CHECK FOR INTERRUPT
BEQ QUIT ;QUIT IF CLEARED BY INTERRUPT
MOVE.L PLANE1(A0),A1 ;GET PLANE1 ADDRESS
MOVE.L #199,D2 ;SET HORIZONTAL SIZE LOOP COUNT
LOOP2A MOVE.L D7,D3 ;RESET LOOP COUNTER
CLR.L D0 ;INITIALIZE BITCATCHER REGISTER
ASL (A1)+ ;START HORIZONTAL SHIFT OF PLANE1
BCC SKIPA ;SKIP IF BIT WAS 0
BSET #0,D0 ;SAVE SHIFTED BIT
SKIPA BTST.B #7,(A1) ;CHECK HIGH BIT OF NEXT WORD
BEQ LOOPA ;SKIP IF BIT WAS 0
BSET.B #0,-1(A1) ;MOVE TO LOW BIT OF PREVIOUS WORD
LOOPA ASL (A1)+ ;SHIFT REST OF LINE IN PLANE1
BTST.B #7,(A1) ;CHECK HIGH BIT OF NEXT WORD
BEQ SKIP3A ;SKIP IF BIT WAS 0
BSET.B #0,-1(A1) ;MOVE TO LOW BIT OF PREVIOUS WORD
SKIP3A DBRA D3,LOOPA ;LOOP TILL LINE DONE
BCLR.B #0,-1(A1) ;CLEAR LOW BIT OF LAST WORD
BTST #0,D0 ;CHECK FOR BIT FROM START OF LINE
BEQ SKIP5A ;SKIP IF BIT WAS 0
ADDQ.B #1,-1(A1) ;SHIFT BIT FROM START OF LINE
SKIP5A DBRA D2,LOOP2A ;LOOP TILL PLANES ARE SHIFTED
DBRA D6,LOOP3A ;LOOP TILL SCROLL DONE
;SCROLL PLANE2
MOVE.L #318,D6 ;TOTAL REPETITIONS
LOOP3B TST.W SIZE(A0) ;CHECK FOR INTERRUPT
BEQ QUIT ;QUIT IF CLEARED BY INTERRUPT
MOVE.L PLANE2(A0),A1 ;GET PLANE1 ADDRESS
MOVE.L #199,D2 ;SET HORIZONTAL SIZE LOOP COUNT
LOOP2B MOVE.L D7,D3 ;RESET LOOP COUNTER
CLR.L D0 ;INITIALIZE BITCATCHER REGISTER
ASL (A1)+ ;START HORIZONTAL SHIFT OF PLANE1
BCC SKIPB ;SKIP IF BIT WAS 0
BSET #0,D0 ;SAVE SHIFTED BIT
SKIPB BTST.B #7,(A1) ;CHECK HIGH BIT OF NEXT WORD
BEQ LOOPB ;SKIP IF BIT WAS 0
BSET.B #0,-1(A1) ;MOVE TO LOW BIT OF PREVIOUS WORD
LOOPB ASL (A1)+ ;SHIFT REST OF LINE IN PLANE1
BTST.B #7,(A1) ;CHECK HIGH BIT OF NEXT WORD
BEQ SKIP3B ;SKIP IF BIT WAS 0
BSET.B #0,-1(A1) ;MOVE TO LOW BIT OF PREVIOUS WORD
SKIP3B DBRA D3,LOOPB ;LOOP TILL LINE DONE
BCLR.B #0,-1(A1) ;CLEAR LOW BIT OF LAST WORD
BTST #0,D0 ;CHECK FOR BIT FROM START OF LINE
BEQ SKIP5B ;SKIP IF BIT WAS 0
ADDQ.B #1,-1(A1) ;SHIFT BIT FROM START OF LINE
SKIP5B DBRA D2,LOOP2B ;LOOP TILL PLANES ARE SHIFTED
DBRA D6,LOOP3B ;LOOP TILL SCROLL DONE
QUIT MOVEM.L (A7)+,D0-D7/A0-A6 ;RESTORE REGISTERS FROM STACK
END ;FINISHED SCROLLING
*)
(* Note-This routine could be shortened by reusing the single plane
scroll for both planes. Don't know why we didn't do it. *)
(* Note2-Actually the whole routine could be much shorter and quicker,
but what's the point. It'll never beat the Blitter *)
(* Note3-Some of the code and techniques in the main Program may be
questionable?? Just experimenting--The real objective was to
demonstrate a Modula-2 CODE statement. *)